import { serial_port_write } from "./conn";
import { Frame } from "./frame"
import { TYPE_ACK, TYPE_REQ, TYPE_RES, TYPE_SESSION, TYPE_SIMPLE_REQ, TYPE_SIMPLE_RES } from "./proto";
import { CodecType, Uint8 } from "./types";

const respMap = new Map<number, (_: Frame) => void>();
let globalSeq = 0;

const TIMEOUT_STR = "超时";
const SIMPLE_TIMEOUT = 500;
const ACK_TIMEOUT = 500;
const REQ_TIMEOUT = 3000;

const getSeq = () => {
    if (globalSeq > 255) {
        globalSeq = 0;
    }
    const seq = globalSeq;
    globalSeq += 1;
    return seq;
}

export const on_recv = (buf: number[]) => {
    if (buf.length < Frame.MINI_LEN) {
        console.log('invalid frame', buf);
        return;
    }
    let frame = new Frame(buf);
    const id = (frame.type() << 8) + frame.seq();
    const fun = respMap.get(id);
    if (fun == null) {
        return;
    }
    fun(frame);
}

const register = (id: number, timeout: number = 500) => {
    return new Promise<Frame>((resolve, reject) => {
        const timterId = setTimeout(() => {
            respMap.delete(id);
            reject(TIMEOUT_STR);
        }, timeout);
        respMap.set(id, (frame: Frame) => {
            clearTimeout(timterId);
            resolve(frame);
        });
    });
}

export const simpleReq = async (cmd: number, req: CodecType[], res: CodecType[]) => {
    const seq = getSeq();
    const buf = Frame.make( new Uint8(TYPE_SIMPLE_REQ), new Uint8(seq), new Uint8(cmd), ...req);
    const id = (TYPE_SIMPLE_RES << 8) + seq;
    const promise = register(id, SIMPLE_TIMEOUT);
    await serial_port_write(buf);
    const frame = await promise;
    frame.parse(...res);
}

const session = async () => {
    const seq = getSeq();
    const buf = Frame.make(new Uint8(TYPE_SESSION), new Uint8(seq));
    const id = (TYPE_ACK << 8) + seq;
    const promise = register(id, ACK_TIMEOUT);
    await serial_port_write(buf);
    await promise;
}

const waitResponse = async (id: number, p: Promise<Frame>) => {
    
    for (;;) {

        try {
            await p;
        } catch (msg) {
            if (msg != TIMEOUT_STR) {
                throw msg;
            }
            p = register(id, REQ_TIMEOUT);
        }
    }
}

export const request = async (cmd: number, req: CodecType[], res: CodecType[]) => {
    
    await session();

    const seq = getSeq();
    const buf = Frame.make(new Uint8(TYPE_REQ), new Uint8(seq), new Uint8(cmd), ...req);
    const id = (TYPE_ACK << 8) + seq;
    const ack = register(id);
    const resPromise = register((TYPE_RES << 8) + seq, REQ_TIMEOUT);
    await serial_port_write(buf);
    await ack;


}


